Skip to content

Support for Advanced Navigation ANPP format#861

Open
joe-saronic wants to merge 2 commits into
rtklibexplorer:mainfrom
joe-saronic:anpp
Open

Support for Advanced Navigation ANPP format#861
joe-saronic wants to merge 2 commits into
rtklibexplorer:mainfrom
joe-saronic:anpp

Conversation

@joe-saronic
Copy link
Copy Markdown

@joe-saronic joe-saronic commented May 19, 2026

Format defined here: https://docs.advancednavigation.com/boreas-d/ANPP/Advanced%20Navigation%20Packet.htm

This implementation only handles the raw satellite measurements, not the entire protocol.

Receiver selection is done in the same way as for SBF format: the opt field is searched for -RCVR{n}, where n must be an integer in the range [0-255] since ANPP theoretically supports that many antennae per unit.

Testing

Compared raw output from Boreas D-90 parsed with https://github.com/saronic-technologies/liban-rs/ (see saronic-technologies/liban-rs#18) to result of parsing with rust bindings to this library in https://github.com/kpwebb/rtklib-ffi/ (see joe-saronic/rtklib-ffi#1). Verified that obs fragments are handled correctly.

Inspected output of convbin from two receivers in a well-known dataset to ensure correct numbers of observations, constellations, etc. Main dataset can not be provided, but a small edited sample is available in the integration tests of the rust wrapper.

@joe-saronic
Copy link
Copy Markdown
Author

Hi @rtklibexplorer. This is my first PR to RTKLIB. I'm curious if there is any procedure you have for reviewing/merging, and what standard of testing you expect for incoming PRs.

Comment thread src/convrnx.c Outdated
Comment thread src/rcv/adnav.c
Comment thread src/rcv/adnav.c Outdated
Comment thread src/rcv/adnav.c

/* map ANPP (sys, freq_code) to rtklib observation code ---------------------*/
static uint8_t anpp2code(uint8_t anpp_sys, uint8_t freq_code)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add all the documented signals. For BDS it appears to be just B1 B2 B3 and might the following not be the most relevant reference. Would they limit Trimble receiver processing to just these signals, and BDS2 has been largely retired now and there is no reference to the B1C / B2A signals. For QZSS there is no definition for L1C/B for J04 and J08. This reference looks a little stale.
https://docs.advancednavigation.com/boreas-d/ANPP/RawSatelliteDataPacket.htm#Raw_Satellite_Data_Packet

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add. From the bottom of that page: © 2026 Advanced Navigation. All Rights Reserved. Version:2.5.1 Date: 2026-03-24. I know for a fact that it contains the latest version of the messages for 60 and 61 because the firmware including them was recently updated. That being said, I don't discount the possibility that a more recent source is available, so will look around.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I have an off-by-one here

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've cut the Gordian knot here and reached out directly to the source (Advanced Navigation). Hoping this will get resolved soon, since I frankly don't know enough to get these mappings absolutely right.

Comment thread src/rcv/adnav.c Outdated
Comment thread src/rcv/adnav.c
return 0;
}
anpp->time.time = (time_t)U4(p + 4);
anpp->time.sec = U4(p + 8) * 1e-6;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could further abstract these raw packet accesses adding a bounds check if you wanted an extra layer of defence in depth. See for example my branch and the septentrio decoder where it defines e.g. the following, and I have the same for some other decoders in other private branches used for testing.

static uint32_t U4(const raw_t *raw, size_t index) {
  RTKBOUNDSCHECK(raw->buff, sizeof(raw->buff), index + 3);
  RTKBOUNDSCHECK(raw->buff, raw->len, index + 3);
  uint32_t u;
  memcpy(&u, raw->buff + index, 4);
  return u;
}

then this becomes the following and you have a little extra peace of mind, and avoiding pointer arithmetic makes the code easier to translate to a higher level language with bounds checked array access.

anpp->time.time = (time_t)U4(raw, 5 + 4);
anpp->time.sec  = U4(raw, 5 +  8) * 1e-6;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For most of these, the bounds check is overkill since they are fixed known-good offsets. Out of curiosity though, which branch are you referring to. I did not see it among the merged or unmerged ones I spot-checked.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw personally would also like to see it move away from pointer arithmetic. These functions also check against the raw->len and not just the buffer size. https://github.com/ourairquality/RTKLIB/blob/oaq/src/rcv/septentrio.c#L447

Fixed/added signal codes
Added missing OmniStar and NavIC satellites/signals
Simplified variable declarations
Added to CMakeLists
@joe-saronic
Copy link
Copy Markdown
Author

@ourairquality I've addressed as much of your feedback as I could for the moment. Waiting for feedback from Advanced Navigation. In the meantime, I've continued testing this with the rtklib-ffi rust crate I'm augmenting.

Comment thread src/rcv/adnav.c
case ANPP_SYS_GPS:
switch (freq_code) {
case 1: return CODE_L1C; /* L1 C/A */
case 2: return CODE_L1C; /* L1 C */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L1 C is likely rinex L1L (or L1S or L1X)

Comment thread src/rcv/adnav.c
case 1: return CODE_L1C; /* L1 C/A */
case 2: return CODE_L1C; /* L1 C */
case 3: return CODE_L1P; /* L1 P */
case 4: return CODE_L1M; /* L1 M */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this might really be L1M, for non-mil it is likely rinex L1W.

Comment thread src/rcv/adnav.c
case 3: return CODE_L1P; /* L1 P */
case 4: return CODE_L1M; /* L1 M */
case 5: return CODE_L2C; /* L2 C */
case 6: return CODE_L2P; /* L2 P */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess rinex L2W, except dup of L2M below?

Comment thread src/rcv/adnav.c
case 5: return CODE_L2C; /* L2 C */
case 6: return CODE_L2P; /* L2 P */
case 7: return CODE_L2M; /* L2 M */
case 8: return CODE_L5X; /* L5 */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If their receiver is Trimble based then 5X otherwise guess L5Q.

Comment thread src/rcv/adnav.c
case 3: return CODE_L1P; /* G1 P */
case 5: return CODE_L2C; /* G2 C/A */
case 6: return CODE_L2P; /* G2 P */
case 8: return CODE_L3X; /* G3 */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess L3X for Trimble otherwise L3Q.

Comment thread src/rcv/adnav.c
case 4: return CODE_L1M; /* L1 M */
case 5: return CODE_L2C; /* L2 C */
case 6: return CODE_L2P; /* L2 P */
case 7: return CODE_L2M; /* L2 M */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also likely rinex L2W.

Comment thread src/rcv/adnav.c
case 1: return CODE_L1C; /* L1 C/A */
case 2: return CODE_L1C; /* L2 C */
case 3: return CODE_L1Z; /* L1 SAIF */
case 5: return CODE_L2X; /* L2 C */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimble L2X otherwise guess L2L (or L2S).

Comment thread src/rcv/adnav.c
case 2: return CODE_L1C; /* L2 C */
case 3: return CODE_L1Z; /* L1 SAIF */
case 5: return CODE_L2X; /* L2 C */
case 6: return CODE_L6X; /* LEX */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimble L6X otherwise L6L.

Comment thread src/rcv/adnav.c
case 3: return CODE_L1Z; /* L1 SAIF */
case 5: return CODE_L2X; /* L2 C */
case 6: return CODE_L6X; /* LEX */
case 8: return CODE_L5Z; /* L5 ?? */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trimble L5X otherwise L5Q.

Comment thread src/rcv/adnav.c
return 0;
}
anpp->time.time = (time_t)U4(p + 4);
anpp->time.sec = U4(p + 8) * 1e-6;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fwiw personally would also like to see it move away from pointer arithmetic. These functions also check against the raw->len and not just the buffer size. https://github.com/ourairquality/RTKLIB/blob/oaq/src/rcv/septentrio.c#L447

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants